"""

Building and world design commands

"""

from django.conf import settings 
from src.objects.models import ObjectDB, ObjAttribute
from src.players.models import PlayerAttribute
from src.utils import create, utils, debug 
from src.commands.default.muxcommand import MuxCommand

# used by @find
CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS

class ObjManipCommand(MuxCommand):
    """
    This is a parent class for some of the defining objmanip commands
    since they tend to have some more variables to define new objects.

    Each object definition can have several components. First is
    always a name, followed by an optional alias list and finally an
    some optional data, such as a typeclass or a location. A comma ',' 
    separates different objects. Like this:
    
        name1;alias;alias;alias:option, name2;alias;alias ...

    Spaces between all components are stripped. 

    A second situation is attribute manipulation. Such commands
    are simpler and offer combinations

        objname/attr/attr/attr, objname/attr, ...             

    """
    # OBS - this is just a parent - it's not intended to actually be 
    # included in a commandset on its own!
    
    def parse(self):
        """
        We need to expand the default parsing to get all 
        the cases, see the module doc.         
        """
        # get all the normal parsing done (switches etc)
        super(ObjManipCommand, self).parse()

        obj_defs = ([],[])    # stores left- and right-hand side of '=' 
        obj_attrs = ([], [])  #                   "
        
        for iside, arglist in enumerate((self.lhslist, self.rhslist)):
            # lhslist/rhslist is already split by ',' at this point
            for objdef in arglist:
                aliases, option, attrs = [], None, []
                if ':' in objdef:
                    objdef, option = [part.strip() for part in objdef.rsplit(':', 1)]
                if ';' in objdef:
                    objdef, aliases = [part.strip() for part in objdef.split(';', 1)]
                    aliases = [alias.strip() for alias in aliases.split(';') if alias.strip()]
                if '/' in objdef:
                    objdef, attrs = [part.strip() for part in objdef.split('/', 1)]
                    attrs = [part.strip().lower() for part in attrs.split('/') if part.strip()]
                # store data 
                obj_defs[iside].append({"name":objdef, 'option':option, 'aliases':aliases})
                obj_attrs[iside].append({"name":objdef, 'attrs':attrs})

        # store for future access
        self.lhs_objs = obj_defs[0]
        self.rhs_objs = obj_defs[1]
        self.lhs_objattr = obj_attrs[0]
        self.rhs_objattr = obj_attrs[1]


class CmdSetObjAlias(MuxCommand):   
    """
    Adding permanent aliases

    Usage:
      @alias <obj> [= [alias[,alias,alias,...]]]

    Assigns aliases to an object so it can be referenced by more 
    than one name. Assign empty to remove all aliases from object.
    Observe that this is not the same thing as aliases 
    created with the 'alias' command! Aliases set with @alias are 
    changing the object in question, making those aliases usable 
    by everyone. 
    """

    key  = "@alias"
    aliases = "@setobjalias"
    locks = "cmd:perm(setobjalias) or perm(Builders)"
    help_category = "Building"
    
    def func(self):
        "Set the aliases."

        caller = self.caller    

        if not self.lhs:
            string = "Usage: @alias <obj> [= [alias[,alias ...]]]"
            self.caller.msg(string)
            return 
        objname = self.lhs

        # Find the object to receive aliases
        obj = caller.search(objname, global_search=True)
        if not obj:
            return
        if self.rhs == None:
            # no =, so we just list aliases on object.
            aliases = obj.aliases
            if aliases:
                caller.msg("Aliases for '%s': %s" % (obj.key, ", ".join(aliases)))
            else:
                caller.msg("No aliases exist for '%s'." % obj.key)
            return 
        
        if not obj.access(caller, 'edit'):
            caller.msg("You don't have permission to do that.")
            return 
        
        if not self.rhs:
            # we have given an empty =, so delete aliases
            old_aliases = obj.aliases
            if old_aliases:
                caller.msg("Cleared aliases from %s: %s" % (obj.key, ", ".join(old_aliases)))
                del obj.dbobj.aliases 
            else:
                caller.msg("No aliases to clear.")
            return 

        # merge the old and new aliases (if any)
        old_aliases = obj.aliases
        new_aliases = [alias.strip().lower() for alias in self.rhs.split(',') if alias.strip()]
        # make the aliases only appear once 
        old_aliases.extend(new_aliases)
        aliases = list(set(old_aliases))        
        # save back to object.
        obj.aliases = aliases 
        # we treat this as a re-caching (relevant for exits to re-build their exit commands with the correct aliases)
        caller.msg("Aliases for '%s' are now set to %s." % (obj.key, ", ".join(obj.aliases)))

class CmdCopy(ObjManipCommand):    
    """
    @copy - copy objects
    
    Usage:
      @copy[/reset] <original obj> [= new_name][;alias;alias..][:new_location] [,new_name2 ...] 

    switch:
      reset - make a 'clean' copy off the object, thus
              removing any changes that might have been made to the original
              since it was first created. 

    Create one or more copies of an object. If you don't supply any targets, one exact copy
    of the original object will be created with the name *_copy. 
    """

    key = "@copy"
    locks = "cmd:perm(copy) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Uses ObjManipCommand.parse()"

        caller = self.caller
        args = self.args
        if not args:
            caller.msg("Usage: @copy <obj> [=new_name[;alias;alias..]][:new_location] [, new_name2...]") 
            return
    
        if not self.rhs:
            # this has no target =, so an identical new object is created. 
            from_obj_name = self.args            
            from_obj = caller.search(from_obj_name)
            if not from_obj:
                return             
            to_obj_name = "%s_copy" % from_obj_name
            to_obj_aliases = ["%s_copy" % alias for alias in from_obj.aliases]
            copiedobj = ObjectDB.objects.copy_object(from_obj, new_key=to_obj_name, 
                                                     new_aliases=to_obj_aliases)
            if copiedobj:
                string = "Identical copy of %s, named '%s' was created." % (from_obj_name, to_obj_name)
            else:
                string = "There was an error copying %s."
        else:
            # we have specified =. This might mean many object targets 
            from_obj_name = self.lhs_objs[0]['name']
            from_obj = caller.search(from_obj_name)
            if not from_obj:
                return             
            for objdef in self.rhs_objs:                
                # loop through all possible copy-to targets                
                to_obj_name = objdef['name']
                to_obj_aliases = objdef['aliases']
                to_obj_location = objdef['option']
                if to_obj_location:
                    to_obj_location = caller.search(to_obj_location, global_search=True)
                    if not to_obj_location:
                        return

                copiedobj = ObjectDB.objects.copy_object(from_obj, new_key=to_obj_name, 
                                                         new_location=to_obj_location, new_aliases=to_obj_aliases)                 
                if copiedobj:
                    string = "Copied %s to '%s' (aliases: %s)." % (from_obj_name, to_obj_name,
                                                                   to_obj_aliases)
                else:
                    string = "There was an error copying %s to '%s'." % (from_obj_name, 
                                                                         to_obj_name)
        # we are done, echo to user
        caller.msg(string)

class CmdCpAttr(ObjManipCommand):
    """
    @cpattr - copy attributes

    Usage:    
      @cpattr[/switch] <obj>/<attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...]
      @cpattr[/switch] <obj>/<attr> = <obj1> [,<obj2>,<obj3>,...]
      @cpattr[/switch] <attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...]
      @cpattr[/switch] <attr> = <obj1>[,<obj2>,<obj3>,...]

    Switches:
      move - delete the attribute from the source object after copying. 

    Example:
      @cpattr coolness = Anna/chillout, Anna/nicety, Tom/nicety
      ->
      copies the coolness attribute (defined on yourself), to attributes
      on Anna and Tom. 

    Copy the attribute one object to one or more attributes on another object. If
    you don't supply a source object, yourself is used. 
    """
    key = "@cpattr"
    locks = "cmd:perm(cpattr) or perm(Builders)"
    help_category = "Building"
    
    def func(self):
        """
        Do the copying.
        """        
        caller = self.caller
    
        if not self.rhs:
            string = """Usage:
            @cpattr[/switch] <obj>/<attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...]
            @cpattr[/switch] <obj>/<attr> = <obj1> [,<obj2>,<obj3>,...]
            @cpattr[/switch] <attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...]
            @cpattr[/switch] <attr> = <obj1>[,<obj2>,<obj3>,...]"""
            caller.msg(string)
            return

        lhs_objattr = self.lhs_objattr
        to_objs = self.rhs_objattr
        from_obj_name = lhs_objattr[0]['name']
        from_obj_attrs = lhs_objattr[0]['attrs']

        if not from_obj_attrs:
            # this means the from_obj_name is actually an attribute name on self.
            from_obj_attrs = [from_obj_name]
            from_obj = self.caller
            from_obj_name = self.caller.name
        else:
            from_obj = caller.search(from_obj_name)
        if not from_obj or not to_objs:
            caller.msg("You have to supply both source object and target(s).")
            return 
        if not from_obj.has_attribute(from_obj_attrs[0]):
            caller.msg("%s doesn't have an attribute %s." % (from_obj_name, from_obj_attrs[0]))
            return
        srcvalue = from_obj.get_attribute(from_obj_attrs[0])

        #copy to all to_obj:ects
        if "move" in self.switches:
            string = "Moving "
        else:
            string = "Copying "
        string += "%s/%s (with value %s) ..." % (from_obj_name, from_obj_attrs[0], srcvalue)
                   
        for to_obj in to_objs:            
            to_obj_name = to_obj['name']
            to_obj_attrs = to_obj['attrs']            
            to_obj = caller.search(to_obj_name)
            if not to_obj:
                string += "\nCould not find object '%s'" % to_obj_name
                continue
            for inum, from_attr in enumerate(from_obj_attrs):
                try:
                    to_attr = to_obj_attrs[inum]
                except IndexError:
                    # if there are too few attributes given 
                    # on the to_obj, we copy the original name instead.
                    to_attr = from_attr
                to_obj.set_attribute(to_attr, srcvalue)
                if "move" in self.switches and not (from_obj == to_obj and from_attr == to_attr):
                    from_obj.del_attribute(from_attr)
                    string += "\nMoved %s.%s -> %s.%s." % (from_obj.name, from_attr,
                                                           to_obj_name, to_attr)
                else:
                    string += "\nCopied %s.%s -> %s.%s." % (from_obj.name, from_attr,
                                                            to_obj_name, to_attr)
        caller.msg(string)

class CmdMvAttr(ObjManipCommand):
    """
    @mvattr - move attributes

    Usage:    
      @mvattr[/switch] <obj>/<attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...]
      @mvattr[/switch] <obj>/<attr> = <obj1> [,<obj2>,<obj3>,...]
      @mvattr[/switch] <attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...]
      @mvattr[/switch] <attr> = <obj1>[,<obj2>,<obj3>,...]

    Switches:
      copy - Don't delete the original after moving. 

    Move an attribute from one object to one or more attributes on another object. If
    you don't supply a source object, yourself is used. 
    """
    key = "@mvattr"
    locks = "cmd:perm(mvattr) or perm(Builders)"
    help_category = "Building"
    
    def func(self):
        """
        Do the moving
        """        
        if not self.rhs:
            string = """Usage:    
      @mvattr[/switch] <obj>/<attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...]
      @mvattr[/switch] <obj>/<attr> = <obj1> [,<obj2>,<obj3>,...]
      @mvattr[/switch] <attr> = <obj1>/<attr1> [,<obj2>/<attr2>,<obj3>/<attr3>,...]
      @mvattr[/switch] <attr> = <obj1>[,<obj2>,<obj3>,...]"""
            self.caller.msg(string)
            return 

        # simply use @cpattr for all the functionality 
        if "copy" in self.switches:
            self.caller.execute_cmd("@cpattr %s" % self.args)
        else:
            self.caller.execute_cmd("@cpattr/move %s" % self.args)

class CmdCreate(ObjManipCommand):
    """
    @create - create new objects

    Usage:
      @create[/drop] objname[;alias;alias...][:typeclass], objname...

    switch:
       drop - automatically drop the new object into your current location (this is not echoed)
              this also sets the new object's home to the current location rather than to you.

    Creates one or more new objects. If typeclass is given, the object
    is created as a child of this typeclass. The typeclass script is
    assumed to be located under game/gamesrc/types and any further
    directory structure is given in Python notation. So if you have a
    correct typeclass object defined in
    game/gamesrc/types/examples/red_button.py, you could create a new
    object of this type like this: 

       @create button;red : examples.red_button.RedButton       

    """

    key = "@create"
    locks = "cmd:perm(create) or perm(Builders)"
    help_category = "Building"
            
    def func(self):
        """
        Creates the object.
        """
    
        caller = self.caller

        if not self.args:
            string = "Usage: @create[/drop] <newname>[;alias;alias...] [:typeclass_path]"
            caller.msg(string)    
            return
            
        # create the objects
        for objdef in self.lhs_objs:            
            string = ""
            name = objdef['name']
            aliases = objdef['aliases']
            typeclass = objdef['option']            
            
            # create object (if not a valid typeclass, the default
            # object typeclass will automatically be used)
            lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (caller.id, caller.id)
            obj = create.create_object(typeclass, name, caller, 
                                       home=caller, aliases=aliases, locks=lockstring)
            if not obj:
                string = "Error when creating object."
                continue
            if aliases: 
                string = "You create a new %s: %s (aliases: %s)."
                string = string % (obj.typeclass.typename, obj.name, ", ".join(aliases))
            else:
                string = "You create a new %s: %s."
                string = string % (obj.typeclass.typename, obj.name)
            # set a default desc
            if not obj.db.desc:
                obj.db.desc = "You see nothing special."
            if 'drop' in self.switches:    
                if caller.location:
                    obj.home = caller.location 
                    obj.move_to(caller.location, quiet=True)
            if string:
                caller.msg(string)


class CmdDebug(MuxCommand):
    """
    Debug game entities

    Usage:
      @debug[/switch] <path to code>

    Switches:
      obj - debug an object
      script - debug a script

    Examples:
      @debug/script game.gamesrc.scripts.myscript.MyScript
      @debug/script myscript.MyScript
      @debug/obj examples.red_button.RedButton

    This command helps when debugging the codes of objects and scripts.
    It creates the given object and runs tests on its hooks. 
    """

    key = "@debug"
    locks = "cmd:perm(debug) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Running the debug"

        if not self.args or not self.switches:
            self.caller.msg("Usage: @debug[/obj][/script] <path>")
            return
        
        path = self.args

        if 'obj' in self.switches or 'object' in self.switches:
            # create and debug the object
            self.caller.msg(debug.debug_object(path, self.caller))
            self.caller.msg(debug.debug_object_scripts(path, self.caller))

        elif 'script' in self.switches:
            self.caller.msg(debug.debug_syntax_script(path))


class CmdDesc(MuxCommand):
    """
    @desc - describe an object or room

    Usage:
      @desc [<obj> =] >description>

    Setts the "desc" attribute on an 
    object. If an object is not given,
    describe the current room. 
    """
    key = "@desc"
    aliases = "@describe"
    locks = "cmd:perm(desc) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Define command"

        caller = self.caller
        if not self.args:
            caller.msg("Usage: @desc [<obj> =] >description>")
            return 

        if self.rhs:
            # We have an =
            obj = caller.search(self.lhs)
            if not obj:
                return 
            desc = self.rhs
        else:
            obj = caller.location
            desc = self.args
        # storing the description
        obj.db.desc = desc
        caller.msg("The description was set on %s." % obj.key)


class CmdDestroy(MuxCommand):
    """
    @destroy - remove objects from the game
    
    Usage: 
       @destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...]
              
    switches:
       override - The @destroy command will usually avoid accidentally destroying
                  player objects. This switch overrides this safety.            
    examples:
       @destroy house, roof, door, 44-78
       @destroy 5-10, flower, 45

    Destroys one or many objects. If dbrefs are used, a range to delete can be
    given, e.g. 4-10. Also the end points will be deleted. 
    """

    key = "@destroy"
    aliases = ["@delete", "@del"]
    locks = "cmd:perm(destroy) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Implements the command."

        caller = self.caller

        if not self.args or not self.lhslist:
            caller.msg("Usage: @destroy[/switches] [obj, obj2, obj3, [dbref-dbref],...]")
            return ""

        def delobj(objname):
            # helper function for deleting a single object
            string = ""
            obj = caller.search(objname)
            if not obj:                
                self.caller.msg(" (Objects to destroy must either be local or specified with a unique dbref.)")
                return ""
            objname = obj.name
            if not obj.access(caller, 'delete'):
                return "\nYou don't have permission to delete %s." % objname
            if obj.player and not 'override' in self.switches:
                return "\nObject %s is controlled by an active player. Use /override to delete anyway." % objname

            had_exits = hasattr(obj, "exits") and obj.exits 
            had_objs = hasattr(obj, "contents") and any(obj for obj in obj.contents 
                                                        if not (hasattr(obj, "exits") and obj not in obj.exits))
            # do the deletion
            okay = obj.delete()
            if not okay:
                string += "\nERROR: %s not deleted, probably because at_obj_delete() returned False." % objname
            else:
                string += "\n%s was destroyed." % objname
                if had_exits: 
                    string += " Exits to and from %s were destroyed as well." % objname
                if had_objs:
                    string += " Objects inside %s were moved to their homes." % objname
            return string 
    
        string = ""
        for objname in self.lhslist:            
            if '-' in objname:
                # might be a range of dbrefs
                dmin, dmax = [utils.dbref(part) for part in objname.split('-', 1)]
                if dmin and dmax:
                    for dbref in range(int(dmin),int(dmax+1)):
                        string += delobj(str(dbref))
                else:
                    string += delobj(objname)
            else:
                string += delobj(objname)
        if string:
            caller.msg(string.strip())
                    

class CmdDig(ObjManipCommand):
    """
    @dig - build and connect new rooms to the current one

    Usage: 
      @dig[/switches] roomname[;alias;alias...][:typeclass] 
            [= exit_to_there[;alias][:typeclass]] 
               [, exit_to_here[;alias][:typeclass]] 

    Switches:
       tel or teleport - move yourself to the new room

    Examples:
       @dig kitchen = north;n, south;s
       @dig house:myrooms.MyHouseTypeclass
       @dig sheer cliff;cliff;sheer = climb up, climb down

    This command is a convenient way to build rooms quickly; it creates the new room and you can optionally
    set up exits back and forth between your current room and the new one. You can add as many aliases as you
    like to the name of the room and the exits in question; an example would be 'north;no;n'.
    """
    key = "@dig"
    locks = "cmd:perm(dig) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Do the digging. Inherits variables from ObjManipCommand.parse()"
        
        caller = self.caller

        if not self.lhs:
            string = "Usage:@ dig[/teleport] roomname[;alias;alias...][:parent] [= exit_there"
            string += "[;alias;alias..][:parent]] "
            string += "[, exit_back_here[;alias;alias..][:parent]]"
            caller.msg(string)
            return
        
        room = self.lhs_objs[0]

        if not room["name"]:
            caller.msg("You must supply a new room name.")            
            return
        location = caller.location         

        # Create the new room 
        typeclass = room['option']
        if not typeclass:
            typeclass = settings.BASE_ROOM_TYPECLASS

        # create room 
        lockstring = "control:id(%s) or perm(Immortals); delete:id(%s) or perm(Wizards); edit:id(%s) or perm(Wizards)" 
        lockstring = lockstring % (caller.dbref, caller.dbref, caller.dbref)

        new_room = create.create_object(typeclass, room["name"],
                                        aliases=room["aliases"])
        new_room.locks.add(lockstring)
        alias_string = ""
        if new_room.aliases:
            alias_string = " (%s)" % ", ".join(new_room.aliases)
        room_string = "Created room %s(%s)%s of type %s." % (new_room, new_room.dbref, alias_string, typeclass)

        
        # create exit to room 

        exit_to_string = ""
        exit_back_string = ""
                
        if self.rhs_objs:
            to_exit = self.rhs_objs[0]             
            if not to_exit["name"]:
                exit_to_string = \
                    "\nNo exit created to new room."
            elif not location:
                exit_to_string = \
                  "\nYou cannot create an exit from a None-location."    
            else:
                # Build the exit to the new room from the current one
                typeclass = to_exit["option"]
                if not typeclass:
                    typeclass = settings.BASE_EXIT_TYPECLASS                            

                new_to_exit = create.create_object(typeclass, to_exit["name"], location,
                                                   aliases=to_exit["aliases"], 
                                                   locks=lockstring, destination=new_room)
                alias_string = ""
                if new_to_exit.aliases:
                    alias_string = " (%s)" % ", ".join(new_to_exit.aliases)
                exit_to_string = "\nCreated Exit from %s to %s: %s(%s)%s."
                exit_to_string = exit_to_string % (location.name, new_room.name, new_to_exit, 
                                                   new_to_exit.dbref, alias_string)

        # Create exit back from new room 
                
        if len(self.rhs_objs) > 1:
            # Building the exit back to the current room 
            back_exit = self.rhs_objs[1]
            if not back_exit["name"]:
                exit_back_string = \
                    "\nNo back exit created."            
            elif not location:
                exit_back_string = \
                   "\nYou cannot create an exit back to a None-location." 
            else:
                typeclass = back_exit["option"]
                if not typeclass:
                    typeclass = settings.BASE_EXIT_TYPECLASS
                new_back_exit = create.create_object(typeclass, back_exit["name"],
                                                     new_room, aliases=back_exit["aliases"], 
                                                     locks=lockstring, destination=location)
                alias_string = ""
                if new_back_exit.aliases:
                    alias_string = " (%s)" % ", ".join(new_back_exit.aliases)
                exit_back_string = "\nCreated Exit back from %s to %s: %s(%s)%s." 
                exit_back_string = exit_back_string % (new_room.name, location.name, 
                                                       new_back_exit, new_back_exit.dbref, alias_string)
        caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string))
        if new_room and ('teleport' in self.switches or "tel" in self.switches):
            caller.move_to(new_room)

class CmdTunnel(MuxCommand):
    """
    dig in often-used directions

    Usage: 
      @tunnel[/switch] <direction> [= roomname[;alias;alias;...][:typeclass]]

    Switches:
      oneway - do not create an exit back to the current location
      tel - teleport to the newly created room 

    Example:
      @tunnel n
      @tunnel n = house;mike's place;green building
    
    This is a simple way to build using pre-defined directions: 
     {wn,ne,e,se,s,sw,w,nw{n (north, northeast etc)
     {wu,d{n (up and down) 
     {wi,o{n (in and out)
    The full names (north, in, southwest, etc) will always be put as
    main name for the exit, using the abbreviation as an alias (so an
    exit will always be able to be used with both "north" as well as
    "n" for example). Opposite directions will automatically be
    created back from the new room unless the /oneway switch is given.
    For more flexibility and power in creating rooms, use @dig.
    """
    
    key = "@tunnel"
    aliases = ["@tun"]
    locks = "cmd: perm(tunnel) or perm(Builders)"
    help_category = "Building"

    # store the direction, full name and its opposite
    directions = {"n" : ("north", "s"),
                  "ne": ("northeast", "sw"),
                  "e" : ("east", "w"),
                  "se": ("southeast", "nw"),
                  "s" : ("south", "n"),
                  "sw": ("southwest", "ne"),
                  "w" : ("west", "e"),
                  "nw": ("northwest", "se"),
                  "u" : ("up", "d"),
                  "d" : ("down", "u"),
                  "i" : ("in", "o"),
                  "o" : ("out", "i")}
    
    def func(self):
        "Implements the tunnel command"
    
        if not self.args or not self.lhs:
            string = "Usage: @tunnel[/switch] <direction> [= roomname[;alias;alias;...][:typeclass]]"
            self.caller.msg(string)
            return 
        if self.lhs not in self.directions:
            string = "@tunnel can only understand the following directions: %s." % ",".join(sorted(self.directions.keys()))
            string += "\n(use @dig for more freedom)"
            self.caller.msg(string)
            return 
        # retrieve all input and parse it 
        exitshort = self.lhs
        exitname, backshort = self.directions[exitshort]
        backname = self.directions[backshort][0]
    
        roomname = "Some place" 
        if self.rhs:
            roomname = self.rhs # this may include aliases; that's fine.

        telswitch = ""
        if "tel" in self.switches:
            telswitch = "/teleport"
        backstring = ""
        if not "oneway" in self.switches:
            backstring = ", %s;%s" % (backname, backshort)
        
        # build the string we will use to call @dig
        digstring = "@dig%s %s = %s;%s%s" % (telswitch, roomname, exitname, exitshort, backstring)
        self.caller.execute_cmd(digstring)
        
class CmdLink(MuxCommand):    
    """
    @link - connect objects

    Usage:
      @link[/switches] <object> = <target>
      @link[/switches] <object> = 
      @link[/switches] <object> 
     
    Switch:
      twoway - connect two exits. For this to work, BOTH <object>
               and <target> must be exit objects. 

    If <object> is an exit, set its destination to <target>. Two-way operation
    instead sets the destination to the *locations* of the respective given
    arguments. 
    The second form (a lone =) sets the destination to None (same as the @unlink command)
    and the third form (without =) just shows the currently set destination.
    """

    key = "@link"
    locks = "cmd:perm(link) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Perform the link"
        caller = self.caller
        
        if not self.args:
            caller.msg("Usage: @link[/twoway] <object> = <target>")
            return
    
        object_name = self.lhs

        # get object
        obj = caller.search(object_name, global_search=True)        
        if not obj:
            return 

        string = ""
        if self.rhs:
            # this means a target name was given
            target = caller.search(self.rhs, global_search=True)
            if not target:
                return             

            string = ""
            if not obj.destination: 
                string += "Note: %s(%s) did not have a destination set before. Make sure you linked the right thing." % (obj.name,obj.dbref)
            if "twoway" in self.switches:                    
                if not (target.location and obj.location): 
                    string = "To create a two-way link, %s and %s must both have a location" % (obj, target)
                    string += " (i.e. they cannot be rooms, but should be exits)." 
                    self.caller.msg(string)
                    return 
                if not target.destination:
                    string += "\nNote: %s(%s) did not have a destination set before. Make sure you linked the right thing." % (target.name, target.dbref)
                obj.destination = target.location     
                target.destination = obj.location                
                string += "\nLink created %s (in %s) <-> %s (in %s) (two-way)." % (obj.name, obj.location, target.name, target.location)
            else:
                obj.destination = target
                string += "\nLink created %s -> %s (one way)." % (obj.name, target)   

        elif self.rhs == None:
            # this means that no = was given (otherwise rhs 
            # would have been an empty string). So we inspect
            # the home/destination on object
            dest = obj.destination
            if dest:
                string = "%s is an exit to %s." % (obj.name, dest.name)
            else:
                string = "%s is not an exit. Its home location is %s." % obj.home

        else:
            # We gave the command @link 'obj = ' which means we want to
            # clear destination.
            if obj.destination:
                obj.destination = None
                string = "Former exit %s no longer links anywhere." % obj.name
            else:
                string = "%s had no destination to unlink." % obj.name
        # give feedback
        caller.msg(string.strip())

class CmdUnLink(CmdLink):
    """
    @unlink - unconnect objects

    Usage:
      @unlink <Object>

    Unlinks an object, for example an exit, disconnecting
    it from whatever it was connected to.
    """
    # this is just a child of CmdLink

    key = "@unlink"
    locks = "cmd:perm(unlink) or perm(Builders)"
    help_key = "Building"    

    def func(self):
        """
        All we need to do here is to set the right command
        and call func in CmdLink
        """
        
        caller = self.caller
    
        if not self.args:    
            caller.msg("Usage: @unlink <object>")
            return

        # This mimics '@link <obj> = ' which is the same as @unlink
        self.rhs = ""
        
        # call the @link functionality
        super(CmdUnLink, self).func()

class CmdHome(CmdLink):
    """
    @home - control an object's home location

    Usage:
      @home <obj> [= home_location]

    The "home" location is a "safety" location for objects; they
    will be moved there if their current location ceases to exist. All
    objects should always have a home location for this reason. 
    It is also a convenient target of the "home" command. 

    If no location is given, just view the object's home location. 
    """

    key = "@home"
    locks = "cmd:perm(@home) or perm(Builders)"
    help_category = "Building"
    
    def func(self):
        "implement the command"
        if not self.args:
            string = "Usage: @home <obj> [= home_location]"
            self.caller.msg(string)
            return 

        obj = self.caller.search(self.lhs, global_search=True)
        if not obj:
            return 
        if not self.rhs: 
            # just view 
            home = obj.home 
            if not home:
                string = "This object has no home location set!"
            else:
                string = "%s's current home is %s(%s)." % (obj, home, home.dbref)
        else:
            # set a home location 
            new_home = self.caller.search(self.rhs, global_search=True)
            if not new_home:
                return 
            old_home = obj.home
            obj.home = new_home 
            if old_home:
                string = "%s's home location was changed from %s(%s) to %s(%s)." % (obj, old_home, old_home.dbref, new_home, new_home.dbref)
            else:
                string = "%s' home location was set to %s(%s)." % (obj, new_home, new_home.dbref)
        self.caller.msg(string)

class CmdListCmdSets(MuxCommand):
    """
    list command sets on an object

    Usage:
      @cmdsets [obj]

    This displays all cmdsets assigned
    to a user. Defaults to yourself.
    """
    key = "@cmdsets"
    aliases = "@listcmsets"
    locks = "cmd:perm(listcmdsets) or perm(Builders)"
    help_category = "Building"
    
    def func(self):
        "list the cmdsets"

        caller = self.caller
        if self.arglist:
            obj = caller.search(self.arglist[0]) 
            if not obj:
                return 
        else:
            obj = caller
        string = "%s" % obj.cmdset 
        caller.msg(string)

class CmdName(ObjManipCommand):
    """
    cname - change the name and/or aliases of an object
    
    Usage: 
      @name obj = name;alias1;alias2     
    
    Rename an object to something new.

    """
    
    key = "@name"
    aliases = ["@rename"]
    locks = "cmd:perm(rename) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "change the name"

        caller = self.caller
        if not self.args:
            string = "Usage: @name <obj> = <newname>[;alias;alias;...]"
            caller.msg(string)
            return 
        
        if self.lhs_objs:            
            objname = self.lhs_objs[0]['name']
            obj = caller.search(objname)
            if not obj:
                return 
        if self.rhs_objs:
            newname = self.rhs_objs[0]['name']
            aliases = self.rhs_objs[0]['aliases']
        else:
            newname = self.rhs
            aliases = None 
        if not newname and not aliases:
            caller.msg("No names or aliases defined!")            
            return 
        # change the name and set aliases:
        if newname:
            obj.name = newname
        astring = ""
        if aliases:
            obj.aliases = aliases 
            astring = " (%s)" % (", ".join(aliases))
        caller.msg("Object's name changed to '%s'%s." % (newname, astring))


class CmdOpen(ObjManipCommand):            
    """    
    @open - create new exit
    
    Usage:
      @open <new exit>[;alias;alias..][:typeclass] [,<return exit>[;alias;..][:typeclass]]] = <destination> 

    Handles the creation of exits. If a destination is given, the exit
    will point there. The <return exit> argument sets up an exit at the
    destination leading back to the current room. Destination name
    can be given both as a #dbref and a name, if that name is globally
    unique. 

    """
    key = "@open"
    locks = "cmd:perm(open) or perm(Builders)"
    help_category = "Building"

    # a custom member method to chug out exits and do checks 
    def create_exit(self, exit_name, location, destination, exit_aliases=None, typeclass=None):
        """
        Helper function to avoid code duplication.
        At this point we know destination is a valid location

        """
        caller = self.caller
        string = ""
        # check if this exit object already exists at the location.
        # we need to ignore errors (so no automatic feedback)since we
        # have to know the result of the search to decide what to do.
        exit_obj = caller.search(exit_name, location=location, ignore_errors=True)
        if len(exit_obj) > 1:
            # give error message and return
            caller.search(exit_name, location=location)
            return
        if exit_obj:
            exit_obj = exit_obj[0]
            if not exit_obj.destination:
                # we are trying to link a non-exit
                string = "'%s' already exists and is not an exit!\nIf you want to convert it "
                string += "to an exit, you must assign an object to the 'destination' property first."
                caller.msg(string % exit_name)
                return None
            # we are re-linking an old exit.
            old_destination = exit_obj.destination
            if old_destination:
                string = "Exit %s already exists." % exit_name
                if old_destination.id != destination.id:
                    # reroute the old exit.
                    exit_obj.destination = destination
                    exit_obj.aliases = exit_aliases
                    string += " Rerouted its old destination '%s' to '%s' and changed aliases." % \
                        (old_destination.name, destination.name)                
                else:
                    string += " It already points to the correct place."

        else:
            # exit does not exist before. Create a new one.            
            if not typeclass:
                typeclass = settings.BASE_EXIT_TYPECLASS
            exit_obj = create.create_object(typeclass, key=exit_name,
                                            location=location,
                                            aliases=exit_aliases)
            if exit_obj:  
                # storing a destination is what makes it an exit!
                exit_obj.destination = destination
                string = "Created new Exit '%s' from %s to %s (aliases: %s)." % (exit_name,location.name,
                                                                                 destination.name,
                                                                                 ", ".join([str(e) for e in exit_aliases]))
            else:
                string = "Error: Exit '%s' not created." % (exit_name)
        # emit results 
        caller.msg(string)
        return exit_obj 

    def func(self):
        """
        This is where the processing starts.
        Uses the ObjManipCommand.parser() for pre-processing 
        as well as the self.create_exit() method.
        """        
        caller = self.caller    
        
        if not self.args or not self.rhs:
            string = "Usage: @open <new exit>[;alias...][:typeclass][,<return exit>[;alias..][:typeclass]]] "
            string += "= <destination>"
            caller.msg(string)
            return
        
        # We must have a location to open an exit
        location = caller.location
        if not location:
            caller.msg("You cannot create an exit from a None-location.")
            return
            
        # obtain needed info from cmdline 

        exit_name = self.lhs_objs[0]['name']
        exit_aliases = self.lhs_objs[0]['aliases']
        exit_typeclass = self.lhs_objs[0]['option']
        dest_name = self.rhs
        
        # first, check so the destination exists.
        destination = caller.search(dest_name, global_search=True)
        if not destination:
            return 

        # Create exit        
        ok = self.create_exit(exit_name, location, destination, exit_aliases, exit_typeclass)
        if not ok:
            # an error; the exit was not created, so we quit.
            return 
        # Create back exit, if any
        if len(self.lhs_objs) > 1:
            back_exit_name = self.lhs_objs[1]['name']
            back_exit_aliases = self.lhs_objs[1]['aliases']
            back_exit_typeclass = self.lhs_objs[1]['option']        
            ok = self.create_exit(back_exit_name, destination, location, back_exit_aliases, back_exit_typeclass)
        
class CmdSetAttribute(ObjManipCommand):
    """
    @set - set attributes

    Usage:
      @set <obj>/<attr> = <value>
      @set <obj>/<attr> =  
      @set <obj>/<attr>
   
    Sets attributes on objects. The second form clears
    a previously set attribute while the last form
    inspects the current value of the attribute 
    (if any). You can also set lists [...] and dicts {...}
    on attributes with @set (but not nested combinations). Also
    note that such lists/dicts will always hold strings (never numbers).
    Use @py if you need to set arbitrary lists and dicts.
    """

    key = "@set"
    locks = "cmd:perm(set) or perm(Builders)"
    help_category = "Building"
    
    def func(self):
        "Implement the set attribute - a limited form of @py."

        caller = self.caller
        if not self.args:
            caller.msg("Usage: @set obj/attr = value. Use empty value to clear.")
            return        

        # get values prepared by the parser
        value = self.rhs
        objname = self.lhs_objattr[0]['name']
        attrs = self.lhs_objattr[0]['attrs']

        obj = caller.search(objname)
        if not obj:
            return        

        string = ""
        if not value:            
            if self.rhs == None:
                # no = means we inspect the attribute(s)
                if not attrs:
                    attrs = [attr.key for attr in obj.get_all_attributes()]
                for attr in attrs:
                    if obj.has_attribute(attr):
                        string += "\nAttribute %s/%s = %s" % (obj.name, attr, obj.get_attribute(attr))
                    else:
                        string += "\n%s has no attribute '%s'." % (obj.name, attr)
                    # we view it without parsing markup. 
                self.caller.msg(string.strip(), data={"raw":True})
                return 
            else:                
                # deleting the attribute(s)            
                for attr in attrs:
                    if obj.has_attribute(attr):
                        val = obj.get_attribute(attr)
                        obj.del_attribute(attr)                        
                        string += "\nDeleted attribute '%s' (= %s) from %s." % (attr, val, obj.name)                
                    else:
                        string += "\n%s has no attribute '%s'." % (obj.name, attr)            
        else:
            # setting attribute(s)
 
            # analyze if we are trying to set a list or a dict.
            if value.startswith('[') and value.endswith(']'):
                value = value.lstrip('[').rstrip(']').split(',')            
                value = [utils.to_str(val) for val in value]
            elif value.startswith('{') and value.endswith('}') and ':' in value:
                dictpairs = value.lstrip('{').rstrip('}').split(',')            
                try:
                    value = dict([[utils.to_str(p.strip()) for p in pair.split(':')] for pair in dictpairs])
                except Exception:
                    pass 

            for attr in attrs:
                obj.set_attribute(attr, value)
                string += "\nCreated attribute %s/%s = %s" % (obj.name, attr, value)
        # send feedback
        caller.msg(string.strip('\n')) 


class CmdTypeclass(MuxCommand):
    """
    @typeclass - set object typeclass 

    Usage:     
      @typclass[/switch] <object> [= <typeclass path>]
      @type           ''
      @parent         ''

    Switch:
      reset - clean out *all* the attributes on the object - 
              basically making this a new clean object. 
      force - change to the typeclass also if the object
              already has a typeclass of the same name.      
    Example:
      @type button = examples.red_button.RedButton
      
    Sets an object's typeclass. The typeclass must be identified
    by its location using python dot-notation pointing to the correct
    module and class. If no typeclass is given (or a wrong typeclass
    is given), the object will be set to the default typeclass.
    The location of the typeclass module is searched from
    the default typeclass directory, as defined in the server settings.

    """

    key = "@typeclass"
    aliases = "@type, @parent"
    locks = "cmd:perm(typeclass) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Implements command"

        caller = self.caller

        if not self.args:
            caller.msg("Usage: @type <object> [=<typeclass]")
            return

        # get object to swap on
        obj = caller.search(self.lhs)
        if not obj:
            return 

        if not self.rhs:
            # we did not supply a new typeclass, view the 
            # current one instead.
            if hasattr(obj, "typeclass"):                
                string = "%s's current typeclass is '%s'." % (obj.name, obj.typeclass.typename)
            else:
                string = "%s is not a typed object." % obj.name
            caller.msg(string)
            return 

        # we have an =, a typeclass was supplied.
        typeclass = self.rhs

        if not obj.access(caller, 'edit'):
            caller.msg("You are not allowed to do that.")
            return 

        if not hasattr(obj, 'swap_typeclass') or not hasattr(obj, 'typeclass'):
            caller.msg("This object cannot have a type at all!")
            return 
        
        old_path = obj.typeclass_path
        if obj.is_typeclass(typeclass) and not 'force' in self.switches:
            string = "%s already has the typeclass '%s'." % (obj.name, typeclass)
        else:
            reset = "reset" in self.switches
            old_typeclass_name = obj.typeclass.typename
            ok = obj.swap_typeclass(typeclass, clean_attributes=reset)
            if ok:
                string = "%s's type is now %s (instead of %s).\n" % (obj.name, 
                                                                     obj.typeclass.typename,
                                                                     old_typeclass_name)
                if reset:
                    string += "All attributes where reset."                                                               
                else:    
                    string += "Note that the new class type could have overwritten "
                    string += "same-named attributes on the existing object."            
            else:
                string = "Could not swap '%s' (%s) to typeclass '%s'." % (obj.name,
                                                                                  old_typeclass_name,
                                                                                  typeclass)
                
        caller.msg(string)


class CmdWipe(ObjManipCommand):
    """
    @wipe - clears attributes

    Usage:
      @wipe <object>[/attribute[/attribute...]]

    Example:
      @wipe box 
      @wipe box/colour

    Wipes all of an object's attributes, or optionally only those
    matching the given attribute-wildcard search string. 
    """
    key = "@wipe"
    locks = "cmd:perm(wipe) or perm(Builders)"
    help_category = "Building"
                
    def func(self):
        """
        inp is the dict produced in ObjManipCommand.parse()
        """

        caller = self.caller

        if not self.args:
            caller.msg("Usage: @wipe <object>[/attribute/attribute...]")
            return        

        # get the attributes set by our custom parser
        objname = self.lhs_objattr[0]['name']
        attrs = self.lhs_objattr[0]['attrs']
        
        obj = caller.search(objname)
        if not obj:
            return
        if not obj.access(caller, 'edit'):
            caller.msg("You are not allowed to do that.")
            return 
        if not attrs:
            # wipe everything 
            for attr in obj.get_all_attributes():
                attr.delete()            
            string = "Wiped all attributes on %s." % obj.name
        else:            
            for attrname in attrs:
                obj.attr(attrname, delete=True )
            string = "Wiped attributes %s on %s." 
            string = string % (",".join(attrs), obj.name)
        caller.msg(string)

class CmdLock(ObjManipCommand):
    """
    lock - assign a lock definition to an object

    Usage:
      @lock <object>[ = <lockstring>]
      or 
      @lock[/switch] object/<access_type>
      
    Switch:
      del - delete given access type
      view - view lock associated with given access type (default)
    
    If no lockstring is given, shows all locks on
    object. 

    Lockstring is on the form
       'access_type:[NOT] func1(args)[ AND|OR][ NOT] func2(args) ...]
    Where func1, func2 ... valid lockfuncs with or without arguments. 
    Separator expressions need not be capitalized.

    For example: 
       'get: id(25) or perm(Wizards)'
    The 'get' access_type is checked by the get command and will
    an object locked with this string will only be possible to 
    pick up by Wizards or by object with id 25.
    
    You can add several access_types after oneanother by separating
    them by ';', i.e:
       'get:id(25);delete:perm(Builders)'
    """
    key = "@lock"
    aliases = ["@locks", "lock", "locks"]
    locks = "cmd: perm(@locks) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Sets up the command"

        caller = self.caller
        if not self.args: 
            string = "@lock <object>[ = <lockstring>] or @lock[/switch] object/<access_type>"            
            caller.msg(string)
            return 
        if '/' in self.lhs: 
            # call on the form @lock obj/access_type
            objname, access_type = [p.strip() for p in self.lhs.split('/', 1)]            
            obj = caller.search(objname)
            if not obj:
                return 
            lockdef = obj.locks.get(access_type)
            if lockdef: 
                string = lockdef[2]
                if 'del' in self.switches:
                    if not obj.access(caller, 'control'):
                        caller.msg("You are not allowed to do that.")
                        return 
                    obj.locks.delete(access_type)
                    string = "deleted lock %s" % string 
            else:
                string = "%s has no lock of access type '%s'." % (obj, access_type)
            caller.msg(string)
            return 

        if self.rhs: 
            # we have a = separator, so we are assigning a new lock
            objname, lockdef = self.lhs, self.rhs
            obj = caller.search(objname)
            if not obj:
                return 
            if not obj.access(caller, 'control'):
                caller.msg("You are not allowed to do that.")
                return 
            ok = obj.locks.add(lockdef, caller)
            if ok:
                caller.msg("Added lock '%s' to %s." % (lockdef, obj))
            return 

        # if we get here, we are just viewing all locks
        obj = caller.search(self.lhs)
        if not obj:
            return 
        caller.msg(obj.locks)
            
class CmdExamine(ObjManipCommand):
    """    
    examine - detailed info on objects

    Usage: 
      examine [<object>[/attrname]]
      examine [*<player>[/attrname]]

    Switch:
      player - examine a Player (same as adding *)
      raw - don't parse escape codes for data. 

    The examine command shows detailed game info about an
    object and optionally a specific attribute on it. 
    If object is not specified, the current location is examined. 

    Append a * before the search string to examine a player. 

    """
    key = "@examine"
    aliases = ["@ex","ex", "exam", "examine"]
    locks = "cmd:perm(examine) or perm(Builders)"
    help_category = "Building"

    player_mode = False 

    def format_attributes(self, obj, attrname=None, crop=True, raw=False):
        """
        Helper function that returns info about attributes and/or
        non-persistent data stored on object
        """

        headers = {"persistent":"\n{wPersistent attributes{n:",
                   "nonpersistent":"\n{wNon-persistent attributes{n:"}
        headers_noansi = {"persistent":"\nPersistent attributes:",
                          "nonpersistent":"\nNon-persistent attributes:"}
        if raw:
            headers = headers_noansi
        
        if attrname:
            db_attr = [(attrname, obj.attr(attrname))]
            try:
                ndb_attr = [(attrname, object.__getattribute__(obj.ndb, attrname))]
            except Exception:
                ndb_attr = None
        else:
            if self.player_mode:
                db_attr = [(attr.key, attr.value) for attr in PlayerAttribute.objects.filter(db_obj=obj)]
            else:
                db_attr = [(attr.key, attr.value) for attr in ObjAttribute.objects.filter(db_obj=obj)]
            try:
                ndb_attr = [(aname, avalue) for aname, avalue in obj.ndb.__dict__.items()]
            except Exception:
                ndb_attr = None
        string = ""
        if db_attr and db_attr[0]:
            #self.caller.msg(db_attr)
            string += headers["persistent"]
            for attr, value in db_attr:         
                if crop and isinstance(value, basestring):
                    value = utils.crop(value)
                string += "\n %s = %s" % (attr, value)
        if ndb_attr and ndb_attr[0]:
            string += headers["nonpersistent"]
            for attr, value in ndb_attr:
                if crop and isinstance(value, basestring):
                    value = utils.crop(value)
                string += "\n %s = %s" % (attr, value)
        return string 
    
    def format_output(self, obj, raw=False):
        """
        Helper function that creates a nice report about an object.

        returns a string.
        """

        headers = {"name":"\n{wName/key{n: {c%s{n (%s)",
                   "aliases":"\n{wAliases{n: %s",
                   "player":"\n{wPlayer{n: {c%s{n",
                   "playerperms":"\n{wPlayer Perms{n: %s",
                   "typeclass":"\n{wTypeclass{n: %s (%s)",
                   "location":"\n{wLocation{n: %s",
                   "destination":"\n{wDestination{n: %s",
                   "perms":"\n{wPermissions{n: %s",
                   "locks":"\n{wLocks{n:",
                   "cmdset":"\n{wCurrent Cmdset(s){n:\n %s",
                   "cmdset_avail":"\n{wActual commands available to %s (incl. lock-checks, external cmds etc){n:\n %s",
                   "scripts":"\n{wScripts{n:\n %s",
                   "exits":"\n{wExits{n: ",
                   "characters":"\n{wCharacters{n: ",
                   "contents":"\n{wContents{n: "}
        headers_noansi = {"name":"\nName/key: %s (%s)",
                   "aliases":"\nAliases: %s",
                   "player":"\nPlayer: %s",
                   "playerperms":"\nPlayer Perms: %s",
                   "typeclass":"\nTypeclass: %s (%s)",
                   "location":"\nLocation: %s",
                   "destination":"\nDestination: %s",
                   "perms":"\nPermissions: %s",
                   "locks":"\nLocks:",
                   "cmdset":"\nCurrent Cmdset(s):\n %s",
                   "cmdset_avail":"\nActual commands available to %s (incl. lock-checks, external cmds, etc):\n %s",
                   "scripts":"\nScripts:\n %s",
                   "exits":"\nExits: ",
                   "characters":"\nCharacters: ",
                   "contents":"\nContents: "}
        if raw:
            headers = headers_noansi
        
        if hasattr(obj, "has_player") and obj.has_player:
            string = headers["name"] % (obj.name, obj.dbref)        
        else:
            string = headers["name"] % (obj.name, obj.dbref)        
        if hasattr(obj, "aliases") and obj.aliases:
            string += headers["aliases"] % (", ".join(obj.aliases))
        if hasattr(obj, "has_player") and obj.has_player:
            string += headers["player"] % obj.player.name
            perms = obj.player.permissions
            if obj.player.is_superuser:
                perms = ["<Superuser>"]
            elif not perms:
                perms = ["<None>"]                
            string += headers["playerperms"] % (", ".join(perms))         
        string += headers["typeclass"] % (obj.typeclass.typename, obj.typeclass_path)

        if hasattr(obj, "location"):
            string += headers["location"] % obj.location
        if hasattr(obj, "destination") and obj.destination:
            string += headers["destination"]  % obj.destination
        perms = obj.permissions
        if perms:            
            string += headers["perms"] % (", ".join(perms)) 
        locks = str(obj.locks)
        if locks:
            string += headers["locks"] + utils.fill("; ".join([lock for lock in locks.split(';')]), indent=6)

        if not (len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "Empty"):            
            # list the current cmdsets 
            cmdsetstr = "\n".join([utils.fill(cmdset, indent=2) for cmdset in str(obj.cmdset).split("\n")])
            string += headers["cmdset"] % cmdsetstr
    
            # list the actually available commands
            from src.commands.cmdhandler import get_and_merge_cmdsets
            avail_cmdset = get_and_merge_cmdsets(obj)
            avail_cmdset = sorted([cmd.key for cmd in avail_cmdset if cmd.access(obj, "cmd")])
            
            cmdsetstr = utils.fill(", ".join(avail_cmdset), indent=2)                            
            string += headers["cmdset_avail"] % (obj.key, cmdsetstr)

        if hasattr(obj, "scripts") and hasattr(obj.scripts, "all") and obj.scripts.all():
            string += headers["scripts"] % obj.scripts
        # add the attributes                    
        string += self.format_attributes(obj, raw=raw)
        # add the contents                     
        exits = []
        pobjs = []
        things = []
        if hasattr(obj, "contents"):
            for content in obj.contents:
                if content.destination: 
                    exits.append(content)
                elif content.player:
                    pobjs.append(content)
                else:
                    things.append(content)
            if exits:
                string += headers["exits"] + ", ".join([exit.name for exit in exits]) 
            if pobjs:
                string += headers["characters"] + ", ".join(["{c%s{n" % pobj.name for pobj in pobjs])
            if things:
                string += headers["contents"] + ", ".join([cont.name for cont in obj.contents 
                                                           if cont not in exits and cont not in pobjs])
        #output info
        return "-"*78 + '\n' + string.strip() + "\n" + '-'*78
    
    def func(self):
        "Process command"
        caller = self.caller
        
        msgdata = None
        if "raw" in self.switches:
            msgdata = {"raw":True}
            
        if not self.args:
            # If no arguments are provided, examine the invoker's location.
            obj = caller.location
            if not obj.access(caller, 'examine'):
            #If we don't have special info access, just look at the object instead.
                caller.execute_cmd('look %s' % obj.name)
                return              
            string = self.format_output(obj, raw=msgdata)
            caller.msg(string.strip(), data=msgdata)
            return 

        # we have given a specific target object 
        string = ""
        for objdef in self.lhs_objattr:

            obj_name = objdef['name']
            obj_attrs = objdef['attrs']

            
            self.player_mode = "player" in self.switches or obj_name.startswith('*')
            
            obj = caller.search(obj_name, player=self.player_mode)                
            if not obj:
                continue

            if not obj.access(caller, 'examine'):
                #If we don't have special info access, just look at the object instead.
                caller.execute_cmd('look %s' % obj_name)
                continue

            if obj_attrs:
                for attrname in obj_attrs:
                    # we are only interested in specific attributes                    
                    string += self.format_attributes(obj, attrname, crop=False, raw=msgdata)                        
            else:
                string += self.format_output(obj, raw=msgdata)        
        caller.msg(string.strip(), data=msgdata)


class CmdFind(MuxCommand):
    """
    find objects

    Usage:
      @find[/switches] <name or dbref or *player> [= dbrefmin[-dbrefmax]]

    Switches:
      room - only look for rooms (location=None)
      exit - only look for exits (destination!=None)
      char - only look for characters (BASE_CHARACTER_TYPECLASS)

    Searches the database for an object of a particular name or dbref.
    Use *playername to search for a player. The switches allows for
    limiting object matches to certain game entities. Dbrefmin and dbrefmax 
    limits matches to within the given dbrefs, or above/below if only one is given. 
    """ 

    key = "@find"
    aliases = "find, @search, search, @locate, locate"
    locks = "cmd:perm(find) or perm(Builders)"
    help_category = "Building"
            
    def func(self):
        "Search functionality"            
        caller = self.caller
        switches = self.switches

        if not self.args:
            caller.msg("Usage: @find <string> [= low [-high]]")
            return        

        searchstring = self.lhs
        low, high = 1, ObjectDB.objects.all().order_by("-id")[0].id
        if self.rhs:
            if "-" in self.rhs:
                # also support low-high syntax
                limlist = [part.strip() for part in self.rhs.split("-", 1)]
            else:
                # otherwise split by space 
                limlist = self.rhs.split(None, 1)        
            if limlist and limlist[0].isdigit():
                low = max(low, int(limlist[0]))
            if len(limlist) > 1 and limlist[1].isdigit():
                high = min(high, int(limlist[1]))
        low = min(low, high)
        high = max(low, high)

        if searchstring.startswith("*") or utils.dbref(searchstring):
            # A player/dbref search.  
            # run a normal player- or dbref search. This should be unique.

            string = "{wMatch{n(#%i-#%i):" % (low, high)

            result = caller.search(searchstring, global_search=True)
            if not result:
                return        
            if not low <= int(result.id) <= high:
                string += "\n   {RNo match found for '%s' within the given dbref limits.{n" % searchstring
            else:
                string += "\n{g   %s(%s) - %s{n" % (result.key, result.dbref, result.typeclass.path)                            
        else:
            # Not a player/dbref search but a wider search; build a queryset. 
        
            results = ObjectDB.objects.filter(db_key__istartswith=searchstring, id__gte=low, id__lte=high)            
            if "room" in switches:
                results = results.filter(db_location__isnull=True) 
            if "exit" in switches:
                results = results.filter(db_destination__isnull=False)
            if "char" in switches:            
                results = results.filter(db_typeclass_path=CHAR_TYPECLASS)
            nresults = results.count()
            if not nresults:
                # no matches on the keys. Try aliases instead.
                results = results = ObjectDB.alias_set.related.model.objects.filter(db_key=searchstring)
                if "room" in switches:
                    results = results.filter(db_obj__db_location__isnull=True)
                if "exit" in switches:
                    results = results.filter(db_obj__db_destination__isnull=False)
                if "char" in switches:
                    results = results.filter(db_obj__db_typeclass_path=CHAR_TYPECLASS)
                # we have to parse alias -> real object here
                results = [result.db_obj for result in results]
                nresults = len(results)

            restrictions = ""
            if self.switches:
                restrictions = ", %s" % (",".join(self.switches))                
            if nresults:
                # convert result to typeclasses. 
                results = [result.typeclass for result in results]                 
                if nresults > 1:                    
                    string = "{w%i Matches{n(#%i-#%i%s):" % (nresults, low, high, restrictions)
                    for res in results:
                        string += "\n   {g%s(%s) - %s{n" % (res.key, res.dbref, res.path)
                else:
                    string = "{wOne Match{n(#%i-#%i%s):" % (low, high, restrictions)                    
                    string += "\n   {g%s(%s) - %s{n" % (results[0].key, results[0].dbref, results[0].path)            
            else:
                string = "{wMatch{n(#%i-#%i%s):" % (low, high, restrictions)                    
                string += "\n   {RNo matches found for '%s'{n" % searchstring

        # send result 
        caller.msg(string.strip())


class CmdTeleport(MuxCommand):
    """
    teleport

    Usage:
      @tel/switch [<object> =] <location>

    Switches:
      quiet  - don't inform the source and target
               locations about the move. 
              
    Teleports an object somewhere. If no object is
    given we are teleporting ourselves. 
    """
    key = "@tel"
    aliases = "@teleport"
    locks = "cmd:perm(teleport) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Performs the teleport"

        caller = self.caller
        args = self.args 
        lhs, rhs = self.lhs, self.rhs
        switches = self.switches

        if not args:
            caller.msg("Usage: teleport[/switches] [<obj> =] <target_loc>|home")
            return    
        # The quiet switch suppresses leaving and arrival messages.
        if "quiet" in switches:
            tel_quietly = True
        else:
            tel_quietly = False

        if rhs:
            obj_to_teleport = caller.search(lhs, global_search=True)
            destination = caller.search(rhs, global_search=True)
        else:
            obj_to_teleport = caller
            destination = caller.search(args, global_search=True)
        if not obj_to_teleport:
            caller.msg("Did not find object to teleport.")
            return
        if not destination:
            caller.msg("Destination not found.")
            return
        if obj_to_teleport == destination:
            caller.msg("You can't teleport an object inside of itself!")
            return
        # try the teleport 
        if obj_to_teleport.move_to(destination, quiet=tel_quietly, emit_to_obj=caller):            
            if obj_to_teleport == caller:
                caller.msg("Teleported to %s." % destination.key)
            else:
                caller.msg("Teleported %s -> %s." % (obj_to_teleport, destination.key))


class CmdScript(MuxCommand):
    """
    attach scripts

    Usage:
      @script[/switch] <obj> [= <script.path or scriptkey>]
    
    Switches:
      start - start all non-running scripts on object, or a given script only
      stop - stop all scripts on objects, or a given script only

    If no script path/key is given, lists all scripts active on the given
    object. 
    Script path can be given from the base location for scripts as given in
    settings. If adding a new script, it will be started automatically (no /start
    switch is needed). Using the /start or /stop switches on an object without 
    specifying a script key/path will start/stop ALL scripts on the object. 
    """
    
    key = "@script"
    aliases = "@addscript"
    locks = "cmd:perm(script) or perm(Wizards)"
    help_category = "Building"

    def func(self):
        "Do stuff"

        caller = self.caller

        if not self.args:
            string = "Usage: @script[/switch] <obj> [= <script.path or script key>]"
            caller.msg(string)
            return 
        
        obj = caller.search(self.lhs)
        if not obj:
            return

        string = ""
        if not self.rhs:
            # no rhs means we want to operate on all scripts
            scripts = obj.scripts.all()
            if not scripts:
                string += "No scripts defined on %s." % obj.key
            elif not self.switches:
                # view all scripts
                from src.commands.default.system import format_script_list
                string += format_script_list(scripts)
            elif "start" in self.switches:                
                num = sum([obj.scripts.start(script.key) for script in scripts])
                string += "%s scripts started on %s." % (num, obj.key)
            elif "stop" in self.switches:               
                for script in scripts:
                    string += "Stopping script %s on %s." % (script.key, obj.key)
                    script.stop()
                string = string.strip()
            obj.scripts.validate()
        else: # rhs exists            
            if not self.switches:
                # adding a new script, and starting it
                ok = obj.scripts.add(self.rhs, autostart=True)
                if not ok:
                    string += "\nScript %s could not be added and/or started on %s." % (self.rhs, obj.key)
                else:
                    string = "Script {w%s{n successfully added and started on %s." % (self.rhs, obj.key)

            else:
                paths = [self.rhs] + ["%s.%s" % (prefix, self.rhs) 
                                      for prefix in settings.SCRIPT_TYPECLASS_PATHS]            
                if "stop" in self.switches:
                    # we are stopping an already existing script            
                    for path in paths:
                        ok = obj.scripts.stop(path)                        
                        if not ok:
                            string += "\nScript %s could not be stopped. Does it exist?" % path
                        else:
                            string = "Script stopped and removed from object."
                            break 
                if "start" in self.switches:
                    # we are starting an already existing script 
                    for path in paths:
                        ok = obj.scripts.start(path)
                        if not ok:
                            string += "\nScript %s could not be (re)started." % path
                        else:
                            string = "Script started successfully."
                            break
        caller.msg(string.strip())
